记一次 flutter 插件注册顺序导致问题的排查

问题描述

跨时空app中,用到了 fluwxappscheme 两个插件,分别用于 微信SDK 接入和监听应用协议(URL Scheme,iOS 中 H5 唤起 app 几乎都用到它)

发现两个插件同时使用时,原先正常的微信分享功能,在 iOS 端就无法使用了。

问题原因

反复重现后,发现问题出在应用首次调用 微信openSDK 的认证过程。

应用唤起微信获取到 token 后,会被微信通过 universal_link 唤起,这时候 fluwx 注册的监听(用于接收微信通过 universal_link 带回的 token 参数)没有被调用,以至于没有将 token 信息保存下来。于是后续应用每次调用 微信openSDK 都由于没有认证信息,而被认为是首次调用,重复上述过程。

根因追溯

所以症结在于 fluwx 的回调,为什么被拦截了。

根据 Flutter 的 objc++ 代码可以看出,对于监听到的传入 URL 处理逻辑是:

遍历 _delegates 如果 url 已处理完成,则跳出循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// https://github.com/flutter/engine/blob/9f650edd14dd0d74acb3d6ad65eb794b1e4b27e3/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm#L312

- (BOOL)application:(UIApplication*)application
openURL:(NSURL*)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id>*)options {
for (NSObject<FlutterApplicationLifeCycleDelegate>* delegate in _delegates) {
if (!delegate) {
continue;
}
if ([delegate respondsToSelector:_cmd]) {
if ([delegate application:application openURL:url options:options]) {
return YES;
}
}
}
return NO;
}

_delegate 则是在 addDelegate: 中被加入的。_delegate 的是一個 PointerArray,他的顺序是在 plugin 被 register 时,调用 addDelegate: 确定下来的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// https://github.com/flutter/engine/blob/9f650edd14dd0d74acb3d6ad65eb794b1e4b27e3/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm#L98

@implementation FlutterPluginAppLifeCycleDelegate {
NSMutableArray* _notificationUnsubscribers;
UIBackgroundTaskIdentifier _debugBackgroundTask;

// Weak references to registered plugins.
NSPointerArray* _delegates;
}

...

- (void)addDelegate:(NSObject<FlutterApplicationLifeCycleDelegate>*)delegate {
[_delegates addPointer:(__bridge void*)delegate];
...
}

而 Flutter plugin 注册的顺序则是由自动生成的文件 GeneratedPluginRegistrant.m 所决定。

1
2
3
4
5
6
7
8
9
//
// Generated file. Do not edit.
//
...
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[FlutterLineSdkPlugin registerWithRegistrar:[registry registrarForPlugin:@"FlutterLineSdkPlugin"]];
[UniLinksPlugin registerWithRegistrar:[registry registrarForPlugin:@"UniLinksPlugin"]];
[FLTFirebaseDynamicLinksPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTFirebaseDynamicLinksPlugin"]];
}

那么 Flutter 是如何生成 GeneratedPluginRegistrant.m 的呢?

1
2
3
4
// https://github.com/flutter/flutter/blob/39d7a019c150ca421b980426e85b254a0ec63ebd/packages/flutter_tools/gradle/flutter.gradle#L248

* The plugins are added to pubspec.yaml. Then, upon running `flutter pub get`,
* the tool generates a `.flutter-plugins` file, which contains a 1:1 map to each plugin location.

每次执行 flutter pub get,时,会生成 .flutter-plugins,而 GeneratedPluginRegistrant.m 就是依照首字母排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// https://github.com/flutter/flutter/blob/fa4d31b31/packages/flutter_tools/lib/src/plugins.dart#L464

const String _objcPluginRegistryImplementationTemplate = '''//
// Generated file. Do not edit.
//
#import "GeneratedPluginRegistrant.h"
{{#plugins}}
#import <{{name}}/{{class}}.h>
{{/plugins}}
@implementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
{{#plugins}}
[{{prefix}}{{class}} registerWithRegistrar:[registry registrarForPlugin:@"{{prefix}}{{class}}"]];
{{/plugins}}
}
@end

这样问题的解决方法就明确了: 修改 appscheme 包的名称,或者改动 Fluter 的逻辑,显然前者的成本要小一些。本地保存一份插件,改名后引用即可,注意及时升级就是了。插件的更新频率,应该低于 Flutter 框架本身。 同时,我还在 appscheme 项目中提了 issue ,期待作者解决。

思考

个人认为,flutter 成功的原因有两个重要方面:其一是跨端 UI 渲染的高性能和一致性;另一个就是完备的插件扩展机制和生态。

前者已被广泛吹捧,这里无需赘述。后者则极大方便了应用功能的集成:同样的功能,可能有好几个插件可以实现,开发者选择最合适自身的即可。开发者的选择,又反过来成为插件推荐的最可靠依据。最终让好的插件脱颖而出。

有人说 flutter 是又一 KPI 产物,对此说法本人认知有限,不置可否。但很明显的体会是:flutter 确实极大拉低了应用开发的门槛。不说 UI 渲染和插件机制有多么牛逼。单是开发框架本身,集成的 Kotlin Gradle cocoapods… 工具链,就足以让初学者望而却步,可能还没入门就放弃了。但 Flutter 框架替我们抹平了这一切,让开发者得以专注于应用功能本身。

调试技巧

最后,提供几个调试 iOS 微信openSDK 到小技巧:

首次调用 微信openSDK ,会多一个 APP 与微信互相跳转的认证过程。认证成功后,就不会再有这个过程,即便是 APP 重装、切换登陆的微信号也不会,除非重装微信,或者修改 universal_link 值。所以如果要调试首次认证过程的话,我会选择修改 universal_link 记录值的方式。
需要修改两处:

  • 一是在开放平台改。改后需要结束微信进程后重启,新的记录值生效;
  • 二是在 APP 代码侧修改,覆盖安装,或卸载重装 APP 。

交流反馈群

跨时空APP交流群

分享到:

评论完整模式加载中...如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理